/*
  ShiftRegister74HC595.cpp - Library for simplified control of 74HC595 shift registers.
  Created by Timo Denk (www.timodenk.com), Nov 2014.
  Additional information is available at http://shiftregister.simsso.de/
  Released into the public domain.
*/

#include "Arduino.h"
#include "ShiftRegister74HC595.h"

// ShiftRegister74HC595 constructor
ShiftRegister74HC595::ShiftRegister74HC595(int numberOfShiftRegisters, int serialDataPin, int clockPin, int latchPin)
{
    // set attributes
    _numberOfShiftRegisters = numberOfShiftRegisters;

    _clockPin = clockPin;
    _serialDataPin = serialDataPin;
    _latchPin = latchPin;
}
void ShiftRegister74HC595::begin()
{
    // define pins as outputs
    pinMode(_clockPin, OUTPUT);
    pinMode(_serialDataPin, OUTPUT);
    pinMode(_latchPin, OUTPUT);

    // set pins low
    digitalWrite(_clockPin, LOW);
    digitalWrite(_serialDataPin, LOW);
    digitalWrite(_latchPin, LOW);

    // allocates the specified number of bytes and initializes them to zero
    _digitalValues = (uint8_t *)malloc(_numberOfShiftRegisters * sizeof(uint8_t));
    memset(_digitalValues, 0, _numberOfShiftRegisters * sizeof(uint8_t));

    updateRegisters(); // reset shift register
}

// ShiftRegister74HC595 destructor
// The memory allocated in the constructor is also released.
ShiftRegister74HC595::~ShiftRegister74HC595()
{
    free(_digitalValues);
}

// Set all pins of the shift registers at once.
// digitalVAlues is a uint8_t array where the length is equal to the number of shift registers.
void ShiftRegister74HC595::setAll(const uint8_t *digitalValues)
{
    memcpy(_digitalValues, digitalValues, _numberOfShiftRegisters); // dest, src, size
    updateRegisters();
}

// Experimental
// The same as setAll, but the data is located in PROGMEM
// For example with:
//     const uint8_t myFlashData[] PROGMEM = { 0x0F, 0x81 };
#ifdef __AVR__
void ShiftRegister74HC595::setAll_P(const uint8_t *digitalValuesProgmem)
{
    PGM_VOID_P p = reinterpret_cast<PGM_VOID_P>(digitalValuesProgmem);
    memcpy_P(_digitalValues, p, _numberOfShiftRegisters);
    updateRegisters();
}
#endif

// Retrieve all states of the shift registers' output pins.
// The returned array's length is equal to the numbe of shift registers.
uint8_t *ShiftRegister74HC595::getAll()
{
    return _digitalValues;
}

// Set a specific pin to either HIGH (1) or LOW (0).
// The pin parameter is a positive, zero-based integer, indicating which pin to set.
void ShiftRegister74HC595::set(int pin, uint8_t value) {
    setNoUpdate(pin, value);
    updateRegisters();
}

void _shiftOut(uint32_t ulDataPin, uint32_t ulClockPin, uint32_t ulBitOrder, uint32_t ulVal) {
    uint8_t i;

    for (i = 0; i < 8; i++) {
        if (ulBitOrder == LSBFIRST) {
            digitalWrite(ulDataPin, !!(ulVal & (1 << i)));
        } else {
            digitalWrite(ulDataPin, !!(ulVal & (1 << (7 - i))));
        }
        delayMicroseconds(1);
        digitalWrite(ulClockPin, HIGH);
        delayMicroseconds(1);
        digitalWrite(ulClockPin, LOW);
        delayMicroseconds(1);
    }
}
// Updates the shift register pins to the stored output values.
// This is the function that actually writes data into the shift registers of the 74HC595
void ShiftRegister74HC595::updateRegisters()
{
    for (int i = _numberOfShiftRegisters - 1; i >= 0; i--) {
        _shiftOut(_serialDataPin, _clockPin, MSBFIRST, _digitalValues[i]);
        // delayMicroseconds(10);
    }

    digitalWrite(_latchPin, HIGH);
    delayMicroseconds(1);
    digitalWrite(_latchPin, LOW);
    delayMicroseconds(1);
}

// Equivalent to set(int pin, uint8_t value), except the physical shift register is not updated.
// Should be used in combination with updateRegisters().
void ShiftRegister74HC595::setNoUpdate(int pin, uint8_t value)
{
    if (value)
    {
        _digitalValues[pin / 8] |= 1 << (pin % 8);
    }
    else
    {
        _digitalValues[pin / 8] &= ~(1 << (pin % 8));
    }
}

// Returns the state of the given pin.
// Either HIGH (1) or LOW (0)
uint8_t ShiftRegister74HC595::get(int pin)
{
    return (_digitalValues[pin / 8] >> (pin % 8)) & 1;
}

// Sets all pins of all shift registers to HIGH (1).
void ShiftRegister74HC595::setAllHigh()
{
    for (int i = 0; i < _numberOfShiftRegisters; i++)
    {
        _digitalValues[i] = 255;
    }
    updateRegisters();
}

// Sets all pins of all shift registers to LOW (0).
void ShiftRegister74HC595::setAllLow()
{
    for (int i = 0; i < _numberOfShiftRegisters; i++)
    {
        _digitalValues[i] = 0;
    }
    updateRegisters();
}
